package arduinoMonitor;

import java.util.Arrays;

import jssc.*;

import org.eclipse.swt.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.layout.*;
import org.swtchart.*;
import org.swtchart.ILineSeries.PlotSymbolType;
//import javax.swing.*;
import org.swtchart.ISeries.SeriesType;

public class ArduinoMonitor {
	
	private static final int TIMEELEMENTS = 100;
	
	private static final int WIDTH = 1500;
	private static final int HEIGHT = 800;
	
	static int[] buffer;
	static SerialPort serial;
	
	static String currentPort = "";
	static boolean isConnected = false;
	static Label connectStatus; // define this static and globally so we can change it from the button listener
	
	static double[] xValues = new double[TIMEELEMENTS];
	static double[][] yValues = new double[20][TIMEELEMENTS]; // 14 digital + 6 analog, TIMEELEMENTS-element history 
	static Chart[] charts = new Chart[20];
	
	static Display display = Display.getDefault();
	static Shell shell = new Shell(display);
	
	static final String[] graphNames = {"Analog 0",
		"Analog 1",
		"Analog 2",
		"Analog 3",
		"Analog 4",
		"Analog* 5",
		"Digital 0",
		"Digital 1",
		"Digital 2",
		"Digital 3",
		"Digital 4",
		"Digital 5",
		"Digital 6",
		"Digital 7",
		"Digital 8",
		"Digital 9",
		"Digital 10",
		"Digital 11",
		"Digital 12",
		"Digital 13"
	};
	
	public static void main(String[] args) {
		
		buildMainWindow();
		
		buildCharts();
        
		shell.pack();
        shell.open();
        
        while (!shell.isDisposed()) { // while the shell (window) is open
            if (!display.readAndDispatch()) { // if there is no work to do at the moment
            	try {
            		updateCharts();
            	}
            	catch(NullPointerException e) {
            		// null pointer means the buffer is empty
            	}
                display.sleep(); // just chill
            }
            	
        }
        display.dispose();
        
        try {
			serial.closePort();
		}
		catch(SerialPortException e) {
			System.out.println("Error closing virtual COM port!");
			e.printStackTrace();
		}
        catch(NullPointerException e) {
        	// null pointer means that serial was never opened
        }
	}
	
	private static void buildMainWindow() {
		
		shell.setText("Arduino Monitor");
		shell.setSize(WIDTH, HEIGHT);
		shell.setLayout(new GridLayout(6, false));
		
		// label
		Label portLabel = new Label(shell, SWT.NONE);
		portLabel.setText("Monitor Shield Port:");
		GridData labelData = new GridData(SWT.RIGHT, SWT.CENTER, false, false);
		labelData.horizontalSpan = 1;
		portLabel.setLayoutData(labelData);
		
		// port list selector setup
		final List portList = new List(shell, SWT.MULTI | SWT.BORDER);
		GridData portSelectorData = new GridData(SWT.LEFT, SWT.CENTER, false, false);
		portSelectorData.horizontalSpan = 2;
		portList.setLayoutData(portSelectorData);
		
		String[] ports = SerialPortList.getPortNames();
		for(String p : ports) {
			portList.add(p);
		}
		
		portList.addListener(SWT.Selection, new Listener() {
			@Override
			public void handleEvent(Event e) {
				currentPort = Arrays.toString(portList.getSelection());
				currentPort = currentPort.substring(1, currentPort.length()-1);
				System.out.println(currentPort);
			}
		});
		
		// connect button
		Button connectButton = new Button(shell, SWT.PUSH);
		connectButton.setText("Connect");
		connectButton.addListener(SWT.Selection, new Listener() {
			@Override
			public void handleEvent(Event ev) {
				serial = new SerialPort(currentPort); // value found by plugging and unplugging device (may change randomly)
				
				try {
					serial.openPort();
					serial.setParams(333333, 8, 1, 0);
					serial.addEventListener(new SerialPortReader(), SerialPort.MASK_RXCHAR);
					isConnected = true;
					connectStatus.setText("Connected!");
					connectStatus.setBackground(new Color(display, 0, 255, 0));
				}
				catch(SerialPortException e) {
					System.out.println("Error opening virtual COM port!");
					e.printStackTrace();
					isConnected = false;
					connectStatus.setText("Not Connected");
					connectStatus.setBackground(new Color(display, 255, 0, 0));
				}
				System.out.println("Attempting to connect to device on port " + currentPort + "...");
			}
			
		});
		
		// connection status label
		connectStatus = new Label(shell, SWT.BORDER);
		connectStatus.setText("Not Connected");
		GridData statusLabelData = new GridData(SWT.CENTER, SWT.CENTER, true, true);
		statusLabelData.horizontalSpan = 2;
		statusLabelData.minimumHeight = 16;
		statusLabelData.minimumWidth = 100;
		connectStatus.setBackground(new Color(display, 255, 0, 0));
		connectStatus.setFont(new Font(display, "Arial", 16, SWT.BOLD));
		connectStatus.setLayoutData(statusLabelData);
	}
	
	private static void buildCharts() {
		for(double[] d : yValues)
			Arrays.fill(d, 0);
		
		for(int i = 0; i < TIMEELEMENTS; i++) {
			xValues[i] = i;
		}
		
		for(int i = 0; i < 20; i++) {
			charts[i] = new Chart(shell, SWT.BORDER);

			GridData chartGridData = new GridData(SWT.LEFT, SWT.CENTER, true, true);
			// these are good for 1600x900, should make scale off of display/monitor dims
			chartGridData.minimumWidth = 230;
			chartGridData.minimumHeight = 187;
			charts[i].setLayoutData(chartGridData);
			
			charts[i].getTitle().setText(graphNames[i]);
			charts[i].getAxisSet().getXAxis(0).getTitle().setText("t");
	        charts[i].getAxisSet().getYAxis(0).getTitle().setText("V");
	        charts[i].getAxisSet().getXAxis(0).setRange(new Range(0, TIMEELEMENTS));
	        charts[i].getAxisSet().getYAxis(0).setRange(new Range(0, 5.5));
	        
	        charts[i].getLegend().setVisible(false);
	        
	        charts[i].getAxisSet().getXAxis(0).getTick().setFont(new Font(display, "Arial", 6, SWT.NONE));
	        charts[i].getAxisSet().getYAxis(0).getTick().setFont(new Font(display, "Arial", 6, SWT.NONE));
	        charts[i].getTitle().setFont(new Font(display, "Arial", 9, SWT.BOLD));
	        charts[i].getAxisSet().getXAxis(0).getTitle().setFont(new Font(display, "Arial", 8, SWT.BOLD));
	        charts[i].getAxisSet().getYAxis(0).getTitle().setFont(new Font(display, "Arial", 8, SWT.BOLD));

	        ILineSeries scatterSeries = (ILineSeries)charts[i].getSeriesSet().createSeries(SeriesType.LINE, "volts");
	        scatterSeries.setYSeries(yValues[i]);
	        scatterSeries.setSymbolType(PlotSymbolType.NONE);
	        scatterSeries.setLineColor(new Color(display, 0, 0, 0));
	        
	        //charts[i].getAxisSet().adjustRange();
		}
	}
	
	private static void updateCharts() {
		for(int i = 0; i < 20; i++) { 
			System.arraycopy(yValues[i], 0, yValues[i], 1, TIMEELEMENTS-1);
			
			// perform parsing of input data here
			// data in buffer is structured as follows:
			// treat all bytes as unsigned
			// {170, A0, A1, A2, A3, A4, PORTA, PORTB, PORTC}
			switch(i) {
			case 0: // analog 0
				yValues[0][0] = (buffer[1] / 255.0 * 5.0);
				break;
				
			case 1: // analog 1
				yValues[1][0] = (buffer[2] / 255.0 * 5.0);
				break;
				
			case 2: // analog 2
				yValues[2][0] = (buffer[3] / 255.0 * 5.0);
				break;
				
			case 3: // analog 3
				yValues[3][0] = (buffer[4] / 255.0 * 5.0);
				break;
				
			case 4: // analog 4
				yValues[4][0] = (buffer[5] / 255.0 * 5.0);
				break;
				
			case 5: // analog 5 as digital
				yValues[5][0] = ((buffer[6] & 0x10) / 16.0 * 5.0); // 0x10 = 0b00010000 = 16 bit in PORTA
				break;
				
			case 6: // digital 0
				yValues[6][0] = ((buffer[8] & 0x04) / 4.0 * 5.0); // 0x04 = 0b00000100 = 4 bit in PORTC
				break;
				
			case 7: // digital 1
				yValues[7][0] = ((buffer[8] & 0x02) / 2.0 * 5.0); // 0x02 = 0b00000010 = 2 bit in PORTC
				break;
				
			case 8: // digital 2
				yValues[8][0] = ((buffer[8] & 0x01) * 5.0); // 0x01 = 0b00000001 = 1 bit in PORTC
				break;
				
			case 9: // digital 3 (flickers for some reason sometimes)
				yValues[9][0] = ((buffer[6] & 0x40) / 64.0 * 5.0); // 0x40 = 0b01000000 = 64 bit in PORTA
				//System.out.println((int)buffer[5] & 0xFF);
				break;
				
			case 10: // digital 4
				yValues[10][0] = ((buffer[8] & 0x40) / 64.0 * 5.0); // 0x40 = 0b01000000 = 64 bit in PORTC
				break;
				
			case 11: // digital 5
				yValues[11][0] = ((buffer[8] & 0x80) / 128.0 * 5.0); // 0x80 = 0b10000000 = 128 bit in PORTC
				break;
				
			case 12: // digital 6
				yValues[12][0] = ((buffer[7] & 0x01) * 5.0); // 0x01 = 0b00000001 = 1 bit in PORTB
				break;
				
			case 13: // digital 7
				yValues[13][0] = ((buffer[7] & 0x02) / 2.0 * 5.0); // 0x02 = 0b00000010 = 2 bit in PORTB
				break;
				
			case 14: // digital 8
				yValues[14][0] = ((buffer[7] & 0x04) / 4.0 * 5.0); // 0x04 = 0b00000100 = 4 bit in PORTB
				break;
				
			case 15: // digital 9
				yValues[15][0] = ((buffer[7] & 0x08) / 8.0 * 5.0); // 0x08 = 0b00001000 = 8 bit in PORTB
				break;
				
			case 16: // digital 10
				yValues[16][0] = ((buffer[7] & 0x10) / 16.0 * 5.0); // 0x10 = 0b00010000 = 16 bit in PORTB
				break;
				
			case 17: // digital 11
				yValues[17][0] = ((buffer[7] & 0x20) / 32.0 * 5.0); // 0x20 = 0b00100000 = 32 bit in PORTB
				break;
				
			case 18: // digital 12
				yValues[18][0] = ((buffer[7] & 0x40) / 64.0 * 5.0); // 0x40 = 0b01000000 = 64 bit in PORTB
				break;
				
			case 19: // digital 13
				yValues[19][0] = ((buffer[7] & 0x80) / 128.0 * 5.0); // 0x80 = 0b10000000 = 128 bit in PORTB
				break;
			}
			
			//System.out.print("\n");
			//for(int b : buffer)
			//	System.out.print((b) + " ");

	        charts[i].getSeriesSet().getSeries("volts").setYSeries(yValues[i]);
	        charts[i].getSeriesSet().getSeries("volts").setXSeries(xValues);
	        
	        charts[i].redraw(); // mark each chart for redrawing
		}
		shell.update(); // force redraw of all charts
	}
	
	static class SerialPortReader implements SerialPortEventListener {
		public void serialEvent(SerialPortEvent ev) {
			if(ev.isRXCHAR()) {
				if(ev.getEventValue() >= 9) { // if there's at least 9 bytes in the serial buffer
					try {
						int[] tmp = serial.readIntArray(9);
						if(tmp[0] == 0b10101010) // make sure they're in sync
							buffer = tmp;
						else
							serial.purgePort(SerialPort.PURGE_RXCLEAR); // purge and hope the next read is in-sync
						
						if(ev.getEventValue() > 50) // buffer seems to overflow randomly sometimes so this prevents it
							serial.purgePort(SerialPort.PURGE_RXCLEAR);
					}
					catch(SerialPortException e) {
						System.out.println(e);
					}
					//System.out.println(ev.getEventValue());
				}
			}
			else if(ev.isERR()) {
				System.out.println("Serial Event Error! - " + ev.getEventValue());
			}
		}
	}
	
}